/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { pbMeta, ProtobufInterface } from './net-data';
import { BufferWriter } from '../common/buffer-writer';
import { BufferReader } from '../common/buffer-reader';
import { conflict } from '../core';
import { Writer, Reader } from 'protobufjs';

const TAG = 'lcc/network/protocol-manager.ts';

/**
 * @zh
 * 协议根对象
 */
export const protocols = Object.create(null);

/**
 * @zh
 * Protobuf类
 */
interface ProtobufClass {
    /**
     * @zh
     * 创建指定消息
     *
     * @params properties 创建消息的属性
     */
    create: (properties: any) => any;

    /**
     * @zh
     * 编码指定消息对象
     *
     * @params message 消息对象
     */
    encode: (message: any) => Writer;

    /**
     * @zh
     * 解码指定消息
     *
     * @params reader 解码的读取器或者缓冲区
     */
    decode: (reader: Reader | Uint8Array) => any;
}

/**
 * @zh
 * Protobuf管理器<br/>
 * 主要管理协议打包器
 */
class ProtobufManager implements ProtobufInterface {
    /**
     * @zh
     * 单例对象
     */
    private static _instance: ProtobufManager = null!;

    /**
     * @zh
     * 获得单例对象
     */
    static get instance() {
        if (ProtobufManager._instance === null) {
            ProtobufManager._instance = new ProtobufManager();
        }
        return ProtobufManager._instance;
    }

    /**
     * @zh
     * 构造管理器
     */
    private constructor() {}

    /**
     * @zh
     * 协议映射表
     */
    private _protocolMap: { [key: number]: ProtobufClass } = Object.create(null);

    /**
     * @zh
     * 判断是否有协议
     *
     * @param command - 命令号
     */
    hasProtocol(command: number) {
        return this._protocolMap[command] !== null;
    }

    /**
     * @zh
     * 获得协议
     *
     * @param command - 命令号
     */
    getProtocol(command: number) {
        return this._protocolMap[command] as ProtobufClass;
    }

    /**
     * @zh
     * 添加协议
     *
     * @param command - 命令号
     * @param protoClass - 协议类型
     * @param override - 是否覆盖
     */
    addProtocol(command: number, protoClass: ProtobufClass, override = false) {
        if (!override && this._protocolMap[command]) {
            conflict(TAG, `addProtocol command '${command}' conflict`);
        }
        this._protocolMap[command] = protoClass;
    }

    /**
     * @zh
     * 打包协议
     *
     * @param wBuff - 写入缓冲
     * @param command - 命令号
     * @param data - 数据
     */
    packProtocol<T>(wBuff: BufferWriter, command: number, data: T) {
        let protocol = this.getProtocol(command);
        if (protocol) {
            // 用于存在继承关系的结构，添加属性会在protobufjs处理时自动配合结构
            data['_Super'] = data;

            let msg = protocol.create(data);
            let buff = protocol.encode(msg).finish();
            wBuff.writeUShort(buff.byteLength);
            wBuff.writeData(buff);
        }
    }

    /**
     * @zh
     * 解包协议
     *
     * @param rBuff - 读取缓冲
     * @param command - 命令号
     */
    unpackProtocol<T>(rBuff: BufferReader, command: number) {
        let protocol = this.getProtocol(command);
        if (protocol) {
            let byteLength = rBuff.readUShort();
            let buff = rBuff.readData(byteLength);
            let data = protocol.decode(new Uint8Array(buff));

            // 处理数据继承情况
            if (data['_Super']) {
                let _super_ = data['_Super'];
                delete data['_Super'];
                this.mergeExtend(data, _super_);
            }

            return data as T;
        }
    }

    /**
     * @zh
     * 合并继承数据
     *
     * @param data 数据
     * @param superData 继承数据
     */
    private mergeExtend(data: any, superData: any) {
        for (let key in superData) {
            if (key !== '_Super') {
                let destData = data[key];
                if (destData === null || destData === undefined) {
                    data[key] = superData[key];
                }
            }
        }
        let _super_ = superData['_Super'];
        if (_super_) {
            this.mergeExtend(data, _super_);
        }
    }
}

/**
 * @zh
 * 协议管理器实例
 */
export const pbMgr = ProtobufManager.instance;

pbMeta.interface = pbMgr;
